﻿using Furion;
using Furion.DatabaseAccessor;
using Furion.DatabaseAccessor.Extensions;
using Furion.DataEncryption;
using Furion.TaskScheduler;
using Microsoft.Extensions.DependencyInjection;
using Queer.AutoJob.Core.Entity;
using Queer.Util;
using Queer.Util.Helper;
using Queer.Util.Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Queer.AutoJob
{
    /// <summary>
    /// 任务处理中心，增删任务请看 JobDone 方法
    /// </summary>
    public class JobCenter
    {
        #region 公开方法

        /// <summary>
        /// 开始任务
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="firstInit">是否为初始化</param>
        public static void JobStart(AutoJobEntity entity,bool firstInit = false)
        {
            var executeType = entity.ExecuteType.GetValueOrDefault() ? SpareTimeExecuteTypes.Serial : SpareTimeExecuteTypes.Parallel;

            SpareTime.Do(
                entity.CronExpression
                , async (timer, count) => { 
                    await JobRun(timer.WorkerName, count); 
                }
                , cronFormat: CronFormat.IncludeSeconds
                , workerName: DESCEncryption.Encrypt(entity.JobName, "job")
                , executeType: executeType
            );

            // 如果是初始化，则按需立即执行
            if(firstInit && entity.StartNow.GetValueOrDefault())
            {
                SpareTime.DoIt(async () =>
                {
                    await JobRun(DESCEncryption.Encrypt(entity.JobName, "job"), 1);
                });
            }
        }

        /// <summary>
        /// 取消任务
        /// </summary>
        /// <param name="entity"></param>
        public static void JobCancel(AutoJobEntity entity)
        {
            string workName = DESCEncryption.Encrypt(entity.JobName, "job");

            if (SpareTime.GetWorker(workName) != null)
                SpareTime.Cancel(workName);
        }

        /// <summary>
        /// 获取所有任务
        /// </summary>
        public static List<SpareTimer> GetAllJob()
        {
            var workers = SpareTime.GetWorkers();
            return workers.ToList();
        }

        /// <summary>
        /// 销毁所有任务
        /// </summary>
        public static void DisposeJob()
        {
            SpareTime.Dispose();
            LogHelper.Warn("警告，所有后台任务被销毁");
        }

        #endregion

        #region 私有

        /// <summary>
        /// 执行任务
        /// </summary>
        private static async Task JobRun(string workerName, long count)
        {
            try
            {
                // 获取提供器
                var serviceScopeFactory = App.GetService<IServiceScopeFactory>();
                using (var scope = serviceScopeFactory.CreateScope())
                {
                    var services = scope.ServiceProvider;
                    var aJrepository = services.GetService<IRepository<AutoJobEntity>>();
                    workerName = DESCEncryption.Decrypt(workerName, "job");
                    var entity = aJrepository.Where(a => a.JobName == workerName).FirstOrDefault();

                    if (entity == null)
                    {
                        LogHelper.Error("遇到一个不存在的任务，请检查。出错任务：" + workerName);
                        return;
                    }

                    // 未到开始时间 或 已经过了结束时间 不予执行
                    if (entity.StartTime > DateTime.Now || (entity.EndTime.HasValue && entity.EndTime < DateTime.Now))
                        return;

                    // 执行代码
                    var obj = await JobDone(services, entity);

                    #region 任务参数修改

                    if (!entity.StartTime.HasValue)
                        entity.StartTime = DateTime.Now;

                    if (!entity.EndTime.HasValue)
                        entity.EndTime = DateTime.MaxValue.AddDays(-1);

                    if (!entity.Count.HasValue)
                        entity.Count = 1;
                    else
                        entity.Count++;

                    entity.StartCount = count;

                    await aJrepository.UpdateNowAsync(entity);

                    #endregion

                    #region 日志记录

                    // 是否需要记录日志
                    if (entity.NeedLog.GetValueOrDefault())
                    {
                        await new AutoJobLogEntity()
                        {
                            JobGroupName = entity.JobGroupName,
                            JobName = entity.JobName,
                            LogStatus = obj.Tag,
                            Remark = obj.Message,
                            UseTime = obj.Data
                        }.InsertNowAsync();
                    }

                    #endregion
                }

            }
            catch (Exception ex)
            {
                LogHelper.Error("定时任务遇到代码异常，请根据异常信息并修复问题", ex);
            }
        }

        /// <summary>
        /// 执行代码
        /// </summary>
        /// <param name="workName"></param>
        /// <returns></returns>
        private static async Task<TData<int>> JobDone(IServiceProvider services, AutoJobEntity entity)
        {
            TData<int> obj;

            Stopwatch sw = new Stopwatch();
            sw.Start();


            // 执行任务
            try
            {
                switch (entity.JobName)
                {
                    case "数据库备份":
                        obj = await services.GetService<DatabasesBackupJob>().Start(entity);
                        break;

                    default:
                        obj = new TData<int>() { Message = "未找到匹配名称的定时任务去执行，任务名称：" + entity.JobName };
                        break;
                }
            }
            catch (Exception ex)
            {
                var msg = $"定时任务“{entity.JobName}”执行遇到错误{LogHelper.GetExceptionMessage(ex)}";
                LogHelper.Error(msg);
                obj = new TData<int>() { Message = msg };
            }

            // 计时器结束
            sw.Stop();

            obj.Data = sw.ElapsedMilliseconds.ParseToInt();

            return obj;
        }

        #endregion
    }
}
